﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using BrightIdeasSoftware;

namespace ROrganizer.UI.Forms.Core
{
    internal class GroupManager<E, T>
    {
        private readonly Dictionary<ToolStripMenuItem, Entry> items;
        private readonly Dictionary<OLVColumn, Entry> columns;
        private readonly ObjectListView olvEntries;
        private readonly Action<T> groupSetter;

        public GroupManager(ObjectListView olvEntries, Action<T> groupSetter = null)
        {
            this.items = new Dictionary<ToolStripMenuItem, Entry>();
            this.columns = new Dictionary<OLVColumn, Entry>();
            this.olvEntries = olvEntries;
            this.groupSetter = groupSetter;
        }

        public void Bind(ToolStripMenuItem item, OLVColumn column, T group = default(T), SortOrder? order = null)
        {
            this.AddIfRequired(item, column, group, order);
            item.Click += this.OnClick;
        }

        private void OnClick(object sender, EventArgs e)
        {
            var item = sender as ToolStripMenuItem;
            var entry = this.items[item];

            this.olvEntries.BeginUpdate();

            try
            {
                foreach (var button in this.items.Keys)
                    if (button != item)
                        button.Checked = false;

                if (item.Checked)
                {
                    this.olvEntries.ShowGroups = true;
                    this.olvEntries.AlwaysGroupByColumn = entry.Column;

                    if (this.groupSetter != null)
                        this.groupSetter(entry.Group);
                }
                else
                {
                    this.olvEntries.AlwaysGroupByColumn = null;
                    this.olvEntries.ShowGroups = false;

                    if (this.groupSetter != null)
                        this.groupSetter(default(T));
                }

                if (entry.Order.HasValue)
                    this.olvEntries.Sort(entry.Column, entry.Order.Value);
            }
            finally
            {
                this.olvEntries.EndUpdate();
            }
        }

        public void SetKey(OLVColumn column, Func<E, object> selector)
        {
            this.AddIfRequired(null, column, default(T), null);
            var entry = this.columns[column];
            entry.Column.GroupKeyGetter = (GroupKeyGetterDelegate)(x => selector((E)x));
        }

        public void SetFormat<V>(OLVColumn column, Func<V, string> transformer)
        {
            this.AddIfRequired(null, column, default(T), null);
            var entry = this.columns[column];
            entry.Formats.Add(typeof(V), transformer);

            if (entry.Column.AspectToStringConverter == null)
                entry.Column.AspectToStringConverter = (AspectToStringConverterDelegate)delegate(object value)
                {
                    if (value == null)
                        return String.Empty;
                    
                    var type = value.GetType();
                    foreach (var format in entry.Formats)
                        if (format.Key.IsAssignableFrom(type))
                            return (string)format.Value.DynamicInvoke(value);

                    return String.Empty;
                };
        }

        public void SetTitle<V>(OLVColumn column, Func<V, string> transformer)
        {
            this.AddIfRequired(null, column, default(T), null);
            var entry = this.columns[column];
            entry.Titles.Add(typeof(V), transformer);

            if (entry.Column.GroupKeyToTitleConverter == null)
                entry.Column.GroupKeyToTitleConverter = (GroupKeyToTitleConverterDelegate)delegate(object value)
                {
                    if (value == null)
                        return (string)entry.Titles.First().Value.DynamicInvoke(value);

                    var type = value.GetType();
                    foreach (var format in entry.Titles)
                        if (format.Key.IsAssignableFrom(type))
                            return (string)format.Value.DynamicInvoke(value);

                    return String.Empty;
                };
        }

        private void AddIfRequired(ToolStripMenuItem item, OLVColumn column, T group, SortOrder? order)
        {
            var entry = new Entry { Column = column, Group = group, Order = order };

            if (item != null && !this.items.ContainsKey(item))
                this.items.Add(item, entry);

            if (!this.columns.ContainsKey(column))
                this.columns.Add(column, entry);
        }

        private class Entry
        {
            public OLVColumn Column;
            public SortOrder? Order;
            public T Group;

            public Dictionary<Type, Delegate> Formats = new Dictionary<Type, Delegate>();
            public Dictionary<Type, Delegate> Titles = new Dictionary<Type, Delegate>();
        }
    }
}
